#include "fileio_util.h"



int rowisgood(IDL_MEMINT row, IDL_MEMINT nrows)
{
  if ( (row < 0) || (row >= nrows) )
    return(0);
  else
    return(1);
}


/* We always make a copy because we need to sort them */
IDL_MEMINT_VECTOR *getrows(IDL_VPTR rowsInputVptr, /* Input rows vptr */
			   IDL_MEMINT nrows)
{

  IDL_MEMINT *rows, i, rows_count, igood, ngood, lastrow;
  IDL_MEMINT_VECTOR *rows_copy;
  int8 rows_converted=0;
  IDL_VPTR rowsVptr;

  IDL_ENSURE_SIMPLE(rowsInputVptr);

  /* Make sure its of type int16 */
  if (rowsInputVptr->type != IDL_TYP_MEMINT) 
    rows_converted = 1;
  
  /* Will return same variable if it is already type int */
  rowsVptr = IDL_CvtMEMINT(1, &rowsInputVptr);
  
  /* The true will ensure simple, which we have already done */
  IDL_VarGetData(rowsVptr, &rows_count, (char **) &rows, IDL_TRUE);

  /* 
     Make a copy 
  */
  rows_copy = IDL_MEMINT_VECTOR_NewFromArray(rows_count, rows);
  if (rows_converted)
    IDL_Deltmp(rowsVptr);

  /* sort rows copy */
  IDL_MEMINT_VECTOR_Sort(rows_copy);
  
  /* 
     Now deal with bad inputs.  I will pull a trick here and copy the good
     ones to the front of the array and set n to ngood
   */

  lastrow = rows_copy->d[0]-1;
  igood = 0;
  ngood = 0;
  for (i=0;i<rows_copy->n;i++)
    {
      if ( (rows_copy->d[i] != lastrow) && rowisgood(rows_copy->d[i], nrows) )
	{
	  rows_copy->d[igood] = rows_copy->d[i];
	  igood++;
	  ngood++;
	}
      lastrow = rows_copy->d[i];
    }

  if (ngood == 0)
    {
      IDL_MEMINT_VECTOR_Free(rows_copy);
      return(NULL);
    }
  else
    {
      rows_copy->n = ngood;
      return(rows_copy);
    }

}

IDL_MEMINT *getcolumns(IDL_VPTR columnsInputVptr,
		       IDL_VPTR *columnsVptr, 
		       IDL_MEMINT *ncolumns,
		       int8 *columns_converted)
{

  IDL_MEMINT *columns, i;

  /* Make sure its a scalar or vector of simple type */

  IDL_ENSURE_SIMPLE(columnsInputVptr);
  
  /* Make sure its of type int16 */

  if (columnsInputVptr->type != IDL_TYP_MEMINT) 
    *columns_converted = 1;
  
  /* Will return same variable if it is already type int */
  *columnsVptr = IDL_CvtMEMINT(1, &columnsInputVptr);
  
  /* The true will ensure simple, which we have already done */
  IDL_VarGetData(*columnsVptr, ncolumns, (char **) &columns, IDL_TRUE);

  /* sort columns */
  qsort(columns, *ncolumns, sizeof(IDL_MEMINT), (void *) IDL_MEMINT_Compare);

  
  return(columns);

}

UCHAR *get_output(IDL_VPTR instructVptr,
		  int columns_there, IDL_VPTR columnsInputVptr,
		  idl_tag_info *input_tag_info,
		  IDL_MEMINT nrows2read,
		  IDL_VPTR *resultVptr,
		  IDL_MEMINT **tagOffsets,
		  int8 **get_column, int verbose)
{

  IDL_VPTR columnsVptr;
  int8 columns_converted=0;
  IDL_MEMINT *columns;
  IDL_MEMINT ncolumns;

  idl_tag_info *tag_info;
  IDL_STRUCT_TAG_DEF *tagdefs;

  UCHAR *dataPtr;
  char mess[100];

  int i,nkeep;

  *get_column = (int8 *) calloc(input_tag_info->ntags, sizeof(int8));
  if (columns_there)
    {
      
      columns = getcolumns(columnsInputVptr, 
			   &columnsVptr, &ncolumns, &columns_converted);
      
      if (verbose) {
	sprintf(mess,"Extracting %d/%d columns",ncolumns,input_tag_info->ntags);
	message(mess);
      }

      /* Create tag definitions from the requested columns */      
      tagdefs = 
	getColumnsTagDef(instructVptr, input_tag_info, columns, ncolumns,
			 *get_column, &nkeep);
	  
      if (tagdefs == NULL)
	{
	  message("No good columns found; exiting");
	  free(*get_column);
	  if (columns_converted)
	    IDL_Deltmp(columnsVptr);
	  return(NULL);
	}

      /* Create output struct. Must initialize if we want to be able to free
	 this memory when we don't copy in or if we want to check any values
	 that haven't been copied in due to errors in reading*/
      dataPtr = 
	IDL_MakeTempStructVector((IDL_StructDefPtr) IDL_MakeStruct(NULL, tagdefs), 
				 nrows2read, 
				 resultVptr, 
				 IDL_TRUE);
	  
      /* Will only use offsets from here */
      tag_info = get_idl_tag_info(*resultVptr, verbose);

      /* Copy out */
      *tagOffsets = (IDL_MEMINT *) calloc(tag_info->ntags, sizeof(IDL_MEMINT));
      for (i=0;i<tag_info->ntags;i++)
	(*tagOffsets)[i] = tag_info->tagOffsets[i];
      
      /* Don't need this stuff any more */
      for (i=0;i<nkeep;i++)
	{
	  if (tagdefs[i].dims) 
	    free(tagdefs[i].dims);
	}

      free(tagdefs);
      free_tag_info(tag_info);

      if (columns_converted)
	IDL_Deltmp(columnsVptr);

    }
  else 
    {

      if (verbose) message("Extracting all columns");

      /* Create output struct. Must initialize since we might have read errors
	 and this would cause error if try to free. */
      dataPtr = 
	IDL_MakeTempStructVector(instructVptr->value.s.sdef, 
				 nrows2read, 
				 resultVptr, 
				 IDL_TRUE);

      /* Copy out */
      *tagOffsets = 
	(IDL_MEMINT *) calloc(input_tag_info->ntags, sizeof(IDL_MEMINT));
      for (i=0;i<input_tag_info->ntags;i++)
	(*tagOffsets)[i] = input_tag_info->tagOffsets[i];

      for (i=0;i<input_tag_info->ntags;i++)
	(*get_column)[i] = 1;

    }

  return(dataPtr);



} /* get_output */





idl_tag_info *get_idl_tag_info(IDL_VPTR structSrc, int verbose)
{
  idl_tag_info *ti;
  int32 i, tag;
  int *tagNelts;

  //IDL_STRING_SLEN_T *tptr;
  IDL_STRING *tptr;

  int64 totbytes=0;

  int debug=0;

  char mess[100];

  /* We only accept structure variables */
  if (structSrc->type != IDL_TYP_STRUCT) 
    {
      error_message("Input structdef must be a structure",NULL);
      return(NULL);
    }

  if (verbose > 1) debug=1;



  ti = (idl_tag_info *) calloc(1, sizeof(idl_tag_info));

  ti->ntags = IDL_StructNumTags(structSrc->value.s.sdef);

  
  ti->tagNames   = calloc(ti->ntags, sizeof(char *));
  ti->tagOffsets = calloc(ti->ntags, sizeof(IDL_MEMINT));
  ti->tagDesc    = calloc(ti->ntags, sizeof(IDL_VPTR));
  ti->row_nbytes = 0;
  ti->tagNbytes  = calloc(ti->ntags, sizeof(int32));
  ti->tagNelts   = calloc(ti->ntags, sizeof(int32));
  ti->buffer     = calloc(ti->ntags, sizeof(char *));

  /* get offsets and descriptions */
  ti->tagNames[0] = 
    IDL_StructTagNameByIndex(structSrc->value.s.sdef, 0, 
			     IDL_MSG_INFO, &ti->structName);


  if (verbose) 
    {
      sprintf(mess,"Getting struct info for struct: %s\nColumns = %d",
	      ti->structName, ti->ntags);
      message(mess);
    }


  i=0;
  for (tag=0; tag< ti->ntags; tag++)
    {
      ti->tagNames[tag] = 
	IDL_StructTagNameByIndex(structSrc->value.s.sdef, tag, 
				 IDL_MSG_INFO, NULL);
      
      ti->tagOffsets[tag] = 
	IDL_StructTagInfoByIndex(structSrc->value.s.sdef, 
				 tag, 
				 IDL_MSG_LONGJMP,
				 &(ti->tagDesc[tag]) );
      
      if (debug)
	printf("    Tag %d = \"%s\" ", tag, ti->tagNames[tag]);

      /* Is this an array? */
      if ( (ti->tagDesc[tag]->flags & IDL_V_ARR) != 0)
	{
	  ti->tagNelts[tag] = ti->tagDesc[tag]->value.arr->n_elts;
	  if (debug)
	    printf(" ARRAY[%d] ", ti->tagNelts[tag]);
	}
      else
	{
	  ti->tagNelts[tag] = 1;
	  if (debug)
	    printf(" SCALAR ");
	}

      if (ti->tagDesc[tag]->type == IDL_TYP_STRING) 
	{

	  tptr = (IDL_STRING *)
	    (structSrc->value.s.arr->data + ti->tagOffsets[tag]);
	  ti->tagNbytes[tag] = tptr->slen;

	  /* Create the buffer */
	  ti->buffer[tag] = (char *) calloc(ti->tagNbytes[tag]+1, sizeof(char));
	  
	}
      else 
	ti->tagNbytes[tag] = idl_type_nbytes(ti->tagDesc[tag]->type);

      ti->row_nbytes += ti->tagNbytes[tag]*ti->tagNelts[tag];

      if (debug) 
	{
	  print_idl_type(ti->tagDesc[tag]->type);
	  printf(" %d",ti->tagNbytes[tag]);
	}

      if (debug)
	printf("\n");
      
    }

  if (debug)
    {
      printf("\n    Counted bytes in struct data: %d\n", ti->row_nbytes);
      printf("    Number of bytes in whole struct def: %d\n\n", 
	     structSrc->value.s.arr->elt_len);
      fflush(stdout);
    }

  return(ti);

}

void
print_idl_type(int type)
{

  switch(type)
    {
    case IDL_TYP_UNDEF: printf("UNDEF"); break;
    case IDL_TYP_BYTE: printf("BYTE"); break;
    case IDL_TYP_INT: printf("INT"); break;
    case IDL_TYP_LONG: printf("LONG"); break;
    case IDL_TYP_FLOAT: printf("FLOAT"); break;
    case IDL_TYP_DOUBLE: printf("DOUBLE"); break;
    case IDL_TYP_COMPLEX: printf("COMPLEX"); break;
    case IDL_TYP_STRING: printf("STRING"); break;
    case IDL_TYP_STRUCT: printf("STRUCT"); break;
    case IDL_TYP_DCOMPLEX: printf("DCOMPLEX"); break;
    case IDL_TYP_PTR: printf("PTR"); break;
    case IDL_TYP_OBJREF: printf("OBJREF"); break;
    case IDL_TYP_UINT: printf("UINT"); break;
    case IDL_TYP_ULONG: printf("ULONG"); break;
    case IDL_TYP_LONG64: printf("LONG64"); break;
    case IDL_TYP_ULONG64: printf("ULONG64"); break;
    default: printf("Unsupported type %d found\n",type); break;
    }
}

int32
idl_type_nbytes(int type)
{

  switch(type)
    {
    case IDL_TYP_UNDEF: return(0);
    case IDL_TYP_BYTE: return(1);
    case IDL_TYP_INT: return(2);
    case IDL_TYP_LONG: return(4);
    case IDL_TYP_FLOAT: return(4);
    case IDL_TYP_DOUBLE: return(8);
    case IDL_TYP_COMPLEX: return(4);
    case IDL_TYP_STRING: return(-1);
    case IDL_TYP_STRUCT: return(-1);
    case IDL_TYP_DCOMPLEX: return(8);
    case IDL_TYP_PTR: return(-1);
    case IDL_TYP_OBJREF: return(-1);
    case IDL_TYP_UINT: return(2);
    case IDL_TYP_ULONG: return(4);
    case IDL_TYP_LONG64: return(8);
    case IDL_TYP_ULONG64: return(8);
    default: printf("Unsupported type %d found\n",type); break;
    }
}


IDL_STRUCT_TAG_DEF *
getColumnsTagDef(IDL_VPTR structSrc, idl_tag_info *tag_info, 
		 IDL_MEMINT *columns, IDL_MEMINT ncolumns, 
		 int8 *get_column, int *nkeep)
{

  IDL_STRUCT_TAG_DEF *tagdefs;
  IDL_MEMINT i, j, column;
  UCHAR n_dim;

  /* Check that the requested columns are in range */
  /* Assuming unique at this point in the code */

  for (i=0;i<tag_info->ntags;i++)
    get_column[i] = 0;


  /* Note this deals properly with duplicates */
  *nkeep = 0;
  for (i=0;i<ncolumns;i++)
    {
      if (columns[i] >= 0 && columns[i] < tag_info->ntags)
	{
	  get_column[ columns[i] ] = 1;
	  *nkeep += 1;
	}
      else
	{
	  char mess[50];
	  sprintf(mess, "Requested column %d is out of range; skipping",columns[i]);
	  message(mess);
	}
    }

  if (*nkeep == 0)
    return(NULL);

  tagdefs = 
    (IDL_STRUCT_TAG_DEF *) calloc(*nkeep, sizeof(IDL_STRUCT_TAG_DEF));
  

  i = 0;
  for (column=0;column<tag_info->ntags;column++)
    {
      if (get_column[ column ])
	{

	  tagdefs[i].name = tag_info->tagNames[column];
	  tagdefs[i].type = 
	    (void *) (( IDL_MEMINT ) tag_info->tagDesc[column]->type);
	  //tagdefs[i].flags = tag_info->tagDesc[column]->flags;
	  tagdefs[i].flags = 0;
	  if ((tag_info->tagDesc[column]->flags & IDL_V_ARR) != 0)
	    {
	      n_dim = tag_info->tagDesc[column]->value.arr->n_dim;
	      tagdefs[i].dims = calloc(n_dim+1, sizeof(IDL_MEMINT));
	      tagdefs[i].dims[0] = n_dim;
	      for (j=1;j<n_dim+1;j++)
		tagdefs[i].dims[j] = tag_info->tagDesc[column]->value.arr->dim[j-1];
	      
	    }
	  i++;
	}
    }

  return(tagdefs);
}



void
free_tag_info(idl_tag_info *tag_info)
{
  int tag;

  free(tag_info->tagNames);
  free(tag_info->tagOffsets);
  free(tag_info->tagDesc);
  free(tag_info->tagNbytes);
  free(tag_info->tagNelts);
  for (tag=0;tag<tag_info->ntags;tag++)
    free(tag_info->buffer[tag]);
  free(tag_info->buffer);
  free(tag_info);
}



void
error_message(char *text, char *errMessage)
{
  
  char errmsg[256];

  if (errMessage) {
    sprintf(errmsg, "Error: %s: %s", text, errMessage);
  } else {
    sprintf(errmsg, "Error: %s", text);
  }
  IDL_Message(IDL_M_NAMED_GENERIC, IDL_MSG_INFO, errmsg);

}

void
error_message_where(int row, int column)
{
  char mess[50];
  sprintf(mess,"Read Error at row %d column: %d",row,column);
  message(mess);
}

void
message(char *text)
{
  IDL_Message(IDL_M_NAMED_GENERIC, IDL_MSG_INFO, text);
}

FILE *get_fileptr(IDL_VPTR fileVptr, char *mode)
{
  char *file;
  FILE *FILEPTR;
  IDL_FILE_STAT stat_blk;
  int unit;

  char test;
  int i;

  /* File must be a string for now */
  if (fileVptr->type == IDL_TYP_STRING)
    {
      /* Get the file name */
      file = IDL_VarGetString(fileVptr);
      
      //    IDL_FileStat()  
      if (!(FILEPTR = fopen(file, mode)))
	error_message("Error opening file",file);
    }
  else 
    {
      IDL_ENSURE_SCALAR(fileVptr);
      unit = (int) IDL_LongScalar(fileVptr);

      IDL_FileStat(unit, &stat_blk);

      if (*mode == 'w' || *mode=='a')
	{
	  if ( !(stat_blk.access & IDL_OPEN_W) &&
	       !(stat_blk.access & IDL_OPEN_APND) &&
	       !(stat_blk.access & IDL_OPEN_NEW) )
	    {
	      error_message("File must be opened for writing",NULL);
	      return(NULL);
	    }
	}
      else if (*mode == 'r')
	{
	  if ( !(stat_blk.access & IDL_OPEN_R) )
	    {
	      error_message("File must be opened for reading",NULL);
	      return(NULL);
	    }
	}
      else
	{
	  char mess[25];
	  sprintf(mess,"Unknown file mode %s", mode);
	  error_message(mess,NULL);
	  return(NULL);
	}

      if ( ! (stat_blk.flags & IDL_F_STDIO) ) 
	error_message("File not opened with /stdio flag.  Reading may fail",NULL);
      
      if ( !(FILEPTR = stat_blk.fptr) )
	error_message("FILEPTR is invalid",NULL);

    }

  return(FILEPTR);
}
